﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Daybreak.Core.SceneGraph;
using Daybreak.Core.Rendering.Materials;
using Daybreak.Core.Rendering.Pipeline;

namespace Daybreak.Core.Rendering
{
    /// <summary>
    /// Performs scene rendering by managing rendering of all render instances
    /// </summary>
    public class RenderQueue
    {
        private static List<PoolBase> avaliablePools = new List<PoolBase>(4);

        abstract class PoolBase
        {
            public int ItemCount;

            public abstract void Reset(bool dispose);
        }

        sealed class ItemPool<T> : PoolBase where T : class, IResetable, ICloneable<T>, new()
        {
            public readonly static ItemPool<T> Instance;

            public readonly T DefaultItem = new T();
            public readonly List<T> Items = new List<T>(128);
            public readonly ResetableQueue<T> Queue = new ResetableQueue<T>(128);

            static ItemPool()
            {
                ItemPool<T>.Instance = new ItemPool<T>();

                RenderQueue.avaliablePools.Add(ItemPool<T>.Instance);
            }

            public ItemPool() { }

            public override void Reset(bool dispose)
            {
                Queue.Reset(dispose);
 
                if (dispose)
                    Items.Clear();

                ItemCount = 0;
            }

            public override string ToString()
            {
                return string.Format("Pool of items: <{0}> count: {1}", typeof(T).Name, ItemCount);
            }
        }

        public abstract class Item<T> : Item, IResetable, ICloneable<T>
        {
            public abstract T Clone();
        }

        public abstract class Item
        {
            /// <summary>
            /// Squared Distance to the camera
            /// </summary>
            public float SquaredDistance;

            /// <summary>
            /// The material to use when rendering this instance.
            /// </summary>
            public abstract IMaterial Material { get; set; }
            /// <summary>
            /// The number of primitives to render.
            /// </summary>
            public abstract int PrimitiveCount { get; set; }

            public abstract void Reset();

            public abstract void CacheNodeTransform(Transform node, ref Matrix matrix);

            public abstract void DrawSelf(GraphicsDevice device);

            public virtual void SetupInstance(GraphicsDevice device, Pipes.ObjectContext objContext)
            {
            }

            public virtual CompositeBatch Optimize(RenderQueue queue, int primitivesPerBatch)
            {
                return null;
            }

            public virtual void BeforeRender(GraphicsDevice device)
            {
            }

            public virtual void AfterRender(GraphicsDevice device)
            {
            }

            public abstract void SetSquaredDistance(ref Vector3 pos);

            public override string ToString()
            {
                return GetType().Name + " " + SquaredDistance;
            }
        }

        private ResetableList<Item> _itemSlots = new ResetableList<Item>(256);

        public RenderQueue() { }

        /// <summary>
        /// Gets called just before each frame
        /// </summary>
        /// <param name="dispose"></param>
        public void ResetQueue(bool dispose)
        {
            for (int i = 0; i < avaliablePools.Count; i++)
                avaliablePools[i].Reset(dispose);

            _itemSlots.Reset(dispose);
        }

        public void Optimize(int primitevesPerBatch)
        {
            int cnt = SlotCount;

            for (int i = 0; i < cnt; i++)
            {
                Item item = _itemSlots.GetAt(i);

                CompositeBatch batch = item.Optimize(this, primitevesPerBatch);

                if (batch != null)
                    _itemSlots.SetAt(i, batch);
            }
        }

        /// <summary>
        /// Allocate a render instance of the type specified for one time use.
        /// </summary>
        /// <typeparam name="T">The type of renderable item</typeparam>
        /// <returns></returns>
        public T AllocateSlot<T>() where T : Item, IResetable, ICloneable<T>, new()
        {
            return AllocateSlot<T>(true);
        }

            /// <summary>
        /// Allocate a render instance of the type specified for one time use.
        /// </summary>
        /// <typeparam name="T">The type of renderable item</typeparam>
        /// <returns></returns>
        public T AllocateSlot<T>(bool add) where T : Item, IResetable, ICloneable<T>, new()
        {
            T item = ItemPool<T>.Instance.Queue.Allocate();

            if (add)
                AddSlot(item);

            return item;

        }

        private void AddSlot(Item item)
        {
            _itemSlots.Add(item);

        }

        public Item GetSlot(int index)
        {
            return _itemSlots.GetAt(index);
        }

        public int SlotCount
        {
            get { return _itemSlots.ItemsCount; }
        }

    }

}
